<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:fb="http://www.facebook.com/2008/fbml">
  <head>
    <title>PokerStats: live tournament poker statistics</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.4.0/dojo/resources/dojo.css"/>
    <link rel="stylesheet" type="text/css" href="http://ajax.googleapis.com/ajax/libs/dojo/1.4.0/dijit/themes/soria/soria.css"/>
    <link rel="stylesheet" type="text/css" href="stats.css"/>
    <style type="text/css">
      #loader {
        position:absolute; top:0; left:0; width:100%; height:100%; z-index:999; vertical-align:middle;
        background:#ededed;
      }
      #loaderInner {
        padding:5px; position:relative; left:0; top:0; width:175px; background:#3c3; color:#fff;
      }
    </style>
  </head>
  <body class="soria">
    <div id="loader">
      <div id="loaderInner">Loading ... </div>
    </div>
    
    <script type="text/javascript" src="http://www.google.com/jsapi"></script>
    <script type="text/javascript">
      google.load("dojo", "1.4.0");
    </script>
    <script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php" type="text/javascript"></script>
    
    <div id="loginDiv" dojoType="dijit.layout.BorderContainer" gutters="false">
      <div dojoType="dijit.layout.BorderContainer" region="top" style="width:100%;height:80px;" gutters="false">
        <div dojoType="dijit.layout.ContentPane" region="left" style="overflow:hidden;">
          <img src="pokerchips.png" alt="poker chips" class="logo"></img>
        </div>
        <div dojoType="dijit.layout.ContentPane" region="center">
          <h1>Welcome to PokerStats</h1>
        </div>
      </div>
      
      <div dojoType="dijit.layout.ContentPane" region="center" class="mainText">
        <h2>To measure is to know.</h2><br />
        <p>PokerStats is built on the idea that poker statistics can be less intuitive, but more fun. After all, PokerStats has:</p>
        <table style="margin-top:-10px">
          <tr>
            <td><img src="graphs.png" alt="gazillion graphs" class="featurepic"></img></td>
            <td>
              <p class="feature">
                <span class="featuretitle">Gazillion graphs</span><br />
                Just when you think you got it right, the next graph will confuse you. 
              </p>
            </td>
          </tr>
          <tr>
            <td><img src="math.png" alt="advanced mathematics" class="featurepic"></img></td>
            <td>
              <p class="feature">
                <span class="featuretitle">Advanced mathematics</span><br />
                Assumes familiarity with stochastic processes, stationary distributions and Bayesian inference.
              </p>
            </td>
          </tr>
          <tr>
            <td><img src="scale.png" alt="horizontal scalability" class="featurepic"></img></td>
            <td>
              <p class="feature">
                <span class="featuretitle">Horizontal scalability</span><br />
                Over <span id="usercount">11.056342</span> regular users (and counting) served by Google App Engine. 
              </p>
            </td>
          </tr>
        </table>
      </div>
      <div dojoType="dijit.layout.ContentPane" region="right">
        <div class="loginBox">
          Sign in to PokerStats with your<br />
          <span class="account">Facebook Account</span><br /><br />
          <fb:login-button></fb:login-button>
        </div>
      </div>
      <div dojoType="dijit.layout.ContentPane" region="bottom">
        <div class="footer">
          &copy;2008 tntforthebrain.com - <a href="http://code.google.com/p/pokerstats-service/">Source Code</a>
        </div> 
      </div>
    </div>    
    
    <div id="mainDiv" dojotype="dijit.layout.BorderContainer">
      <div dojoType="dijit.layout.ContentPane" region="top">
        Period starting at <span id="periodStart"></span>&nbsp;and ending before <span id="periodEnd"></span>.
        Change period: 
        <select id="periodSelect" dojoType="dijit.form.FilteringSelect" onChange="setPeriod" style="width:100px;">
          <option value="2010-1" selected>2010-1</option>
          <option value="2009-4">2009-4</option>
          <option value="2009-3">2009-3</option>
          <option value="2009-2">2009-2</option>
          <option value="2009-1">2009-1</option>
          <option value="2008-4">2008-4</option>
          <option value="2008-3">2008-3</option>
          <option value="2008-2">2008-2</option>
          <option value="All-Time">All-Time</option>
        </select>
        <span id="notification" class="msg" style="display:none;">Loading...</span>
      </div>
        
      <div id="mainTabs" dojoType="dijit.layout.TabContainer" region="center" tabStrip="true">
        <div id="globalTab" dojoType="dijit.layout.ContentPane" title="Global" selected="true" class="tab">
        
           <div dojotype="dijit.layout.BorderContainer" class="statrow3">
             <div id="totals" dojoType="dijit.layout.ContentPane" region="center" class="graph"></div>
             <div dojoType="dijit.layout.ContentPane" region="right" class="desc">
                <strong>Total money won by each player.</strong>
                Answers the question: who won more money?
             </div>
           </div>
             
           <div dojotype="dijit.layout.BorderContainer" class="statrow3">
             <div id="played" dojoType="dijit.layout.ContentPane" region="center" class="graph"></div>
             <div dojoType="dijit.layout.ContentPane" region="right" class="desc">
                <strong>Number of games played.</strong>
                Answers the question: who played more?
             </div>
           </div>
           
           <div dojotype="dijit.layout.BorderContainer" class="statrow3">
             <div id="expectations" dojoType="dijit.layout.ContentPane" region="center" class="graph"></div>
             <div dojoType="dijit.layout.ContentPane" region="right" class="desc">
                <strong>Global expectation.</strong>
                Total money won divided by the number of games played.
                Answers the question: how much can we expect each player to win
                the next time he participates in a game?
             </div>
           </div>
        </div>
        
        <div id="groupsTab" dojoType="dijit.layout.ContentPane" title="Groups" class="tab">
        
           <div dojotype="dijit.layout.BorderContainer" class="statrow3">
             <div id="groupPlayed" dojoType="dijit.layout.ContentPane" region="center" class="graph"></div>
             <div dojoType="dijit.layout.ContentPane" region="right" class="desc">
                <strong>Number of unique player-groups each player played with.</strong>
                A low number indicates that a player usually plays in the same group-setting:
                this can either make life easier or harder for him, depending on whether
                he usually plays against weak or strong players.
             </div>
           </div>
             
           <div dojotype="dijit.layout.BorderContainer" class="statrow3">
             <div id="groupExpectations" dojoType="dijit.layout.ContentPane" region="center" class="graph"></div>
             <div dojoType="dijit.layout.ContentPane" region="right" class="desc">
                <strong>Group expectation.</strong>
                Measures the expectation of each player when playing in a specific
                group-setting, averaged over all group-settings.
                Answers the question: if a player participates in a game with
                a group of players chosen uniformly at random, how much can we expect
                him to win?
             </div>
           </div>
        </div>
        
        <div id="personalTab" dojoType="dijit.layout.ContentPane" title="Personal" class="tab">
        
           <div dojotype="dijit.layout.BorderContainer" class="statrow1">
             <div id="rivals" dojoType="dijit.layout.ContentPane" region="center" class="graph"></div>
             <div dojoType="dijit.layout.ContentPane" region="right" class="desc">
                A <strong>Nemesis</strong> is the player's worst rival: when your
                nemesis participates in a game, you earn less. A
                <strong>Sidekick</strong> is the player's best ally: when your
                sidekick participates in a game, you earn more.
             </div>
           </div>
        </div>
        
        <div id="advancedTab" dojoType="dijit.layout.ContentPane" title="Advanced" class="tab">
        
           <div dojotype="dijit.layout.BorderContainer" class="statrow2">
             <div id="rors" dojoType="dijit.layout.ContentPane" region="center" class="graph"></div>
             <div dojoType="dijit.layout.ContentPane" region="right" class="desc">
                <strong>Survival chance.</strong>
                Measures the complement event of a 
                <a href="http://en.wikipedia.org/wiki/Gambler%27s_ruin">Gambler's Ruin</a>.
                Answers the question: say a given player has an initial bankroll
                of 1000 money units. Whenever he wins, he adds the profits to
                his bankroll and keeps playing. What is the probability that
                the player will never go broke?
             </div>
           </div>
             
           <div dojotype="dijit.layout.BorderContainer" class="statrow2">
             <div id="pokerranks" dojoType="dijit.layout.ContentPane" region="center" class="graph"></div>
             <div dojoType="dijit.layout.ContentPane" region="right" class="desc">
                <strong>PokerRank.</strong>
                The result of applying the
                <a href="http://en.wikipedia.org/wiki/PageRank">PageRank</a> algorithm
                to the players' money-transfers graph.
                Answers the question: say we throw a coin to a random game. After
                a few more games take place, what is the probability that the coin
                will end up in each player's hands?
             </div>
           </div>
        </div>
      </div>
    </div>
    
    <script type="text/javascript">
      function PokerStatsService(url) {
        this.url = url;
      }

      PokerStatsService.prototype.getStats = function(args, callback) {
        dojo.xhrPost( {
          url: this.url,
          handleAs: "json",
          timeout: 30000,
          postData: dojo.toJson(args),
          load: function(response, ioArgs) {
            callback({error: false, obj: response});
            return response;
          },
          error: function(response, ioArgs) {
            callback({error: true});
            return response;
          },
          sync: false,
          preventCache: true
        });
      }
    
      function hideLoader(){
          var loader = dojo.byId("loader");
          dojo.fadeOut({ node: loader, duration:500, onEnd: function() { loader.style.display = "none"; }}).play();
        }
      
      function showNotification(notificationStr){
        var notification = dojo.byId("notification");
        notification.innerHTML = notificationStr;
        notification.style.display = "inline";
        notification.style.opacity = "1";
      }
      
      function hideNotification(){
        var notification = dojo.byId("notification");
        dojo.fadeOut({ node: notification, duration:500, onEnd: function() { notification.style.display = "none"; }}).play();
      }
      
      function periodToDates(periodStr) {
        switch (periodStr) {
          case "2010-1":
            return { fromDateMs: Date.UTC(2010,0,1), toDateMs: Date.UTC(2010,3,1) };
          case "2009-4":
            return { fromDateMs: Date.UTC(2009,9,1), toDateMs: Date.UTC(2010,0,1) };
          case "2009-3":
            return { fromDateMs: Date.UTC(2009,6,1), toDateMs: Date.UTC(2009,9,1) };
          case "2009-2":
            return { fromDateMs: Date.UTC(2009,3,1), toDateMs: Date.UTC(2009,6,1) };
          case "2009-1":
            return { fromDateMs: Date.UTC(2009,0,1), toDateMs: Date.UTC(2009,3,1) };
          case "2008-4":
            return { fromDateMs: Date.UTC(2008,9,1), toDateMs: Date.UTC(2009,0,1) };
          case "2008-3":
            return { fromDateMs: Date.UTC(2008,6,1), toDateMs: Date.UTC(2008,9,1) };
          case "2008-2":
            return { fromDateMs: Date.UTC(2008,3,1), toDateMs: Date.UTC(2008,6,1) };
          case "All-Time":
            return { fromDateMs: Date.UTC(2000,0,1), toDateMs: Date.UTC(2100,0,1) };
          default:
            throw new Error("Invalid period.");
        }
      }
      
      function setPeriod(value) {
        var getStatsParams = periodToDates(value);
        getStatsParams.groupId = 1021;
        
        var webServiceParams = new Object();        
        webServiceParams.method = "getStats";
        webServiceParams.jsonObj = dojo.toJson(getStatsParams);
        
        pokerStatsService.getStats(webServiceParams, getStatsCallback);

        showNotification("Retrieving stats...");
      }
      
      function round(num, dec) {
        return Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec);
      }
      
      function strToDate(str) {
        var slashIdx1 = str.indexOf('/');
        var slashIdx2 = str.lastIndexOf('/');
        
        return new Date(
          str.substring(slashIdx2 + 1),
          str.substring(slashIdx1 + 1, slashIdx2)-1,
          str.substring(0, slashIdx1));
      }
      
      function dateToStr(date) {
        return date.getDate() + "/" + (date.getMonth()+1) + "/" + date.getFullYear();
      }

      function round(num, digits) {
          var tmp = Math.pow(10, digits);
          return Math.round(num * tmp)/tmp;
      }

      function advanceUserCount() {
        var userCountDiv = dojo.byId("usercount");
        var newUserCount = "" + round(parseFloat(userCountDiv.innerHTML) + 0.000004, 6);
        while (newUserCount.length < 9) {
          newUserCount += "0"; 
        }
        userCountDiv.innerHTML = newUserCount;
        
    	setTimeout("advanceUserCount()", 1000);
      } 
      
      function onDojoLoad() {
        dojo.parser.parse();
        dojo.byId("loginDiv").style.display = "block";
        dojo.byId("mainDiv").style.display = "none";

        setTimeout("advanceUserCount()", 1000);
          
        dojo.byId("loaderInner").innerHTML += " done.";
        setTimeout("hideLoader()",250);
        
        FB_RequireFeatures(["Api", "XFBML"], function() {
          FB.Facebook.init("41591e6e807b9451d74d1ea79335a38a", "xd_receiver.htm");
          onFbLoad();
        });
      }
      
      function onFbLoad() {
        FB.Facebook.get_sessionState().waitUntilReady(function() {
          dojo.byId("loginDiv").style.display = "none";
          dojo.byId("mainDiv").style.display = "block";

          pokerStatsService = new PokerStatsService("http://poker.tntforthebrain.com/pokerstats_service");
          setPeriod(dijit.byId("periodSelect").attr("value"));
        });
      }
      
      function getStatsCallback(response, context) {
        if (response.error) {
          showNotification("Could not retrieve stats...");
          return;
        }

        showStats(response.obj);
      }
      
      function isUnique(name, fbFullNamesList) {
        var matches = 0;
        for (var i = 0; i < fbFullNamesList.length; ++i) {
          if (fbFullNamesList[i].substr(0, name.length) == name) {
            ++matches;
          }
        }
        return (matches <= 1);
      }
      
      function getUniqueName(fbUserInfo, fbFullNamesList) {
        // try first name
        var name = fbUserInfo.first_name;
        if (isUnique(name, fbFullNamesList)) {
          return name;
        }
        
        // if required, add characters from last name
        name += " ";
        for (var i = 0; i < fbUserInfo.last_name.length; ++i) {
          name += fbUserInfo.last_name.charAt(i);
          if (isUnique(name, fbFullNamesList)) {
            return name;
          }
        }
        
        // return full name
        return name;
      }
      
      function getUniqueNames(fbUserInfoList) {
        var fbFullNamesList = new Array();
        for (var i = 0; i < fbUserInfoList.length; ++i) {
            fbFullNamesList.push(
              fbUserInfoList[i].first_name + " " + fbUserInfoList[i].last_name);
        };
          
        var fbUniqueNames = new Object();
        for (var i = 0; i < fbUserInfoList.length; ++i) {
          fbUniqueNames[fbUserInfoList[i].uid] =
            getUniqueName(fbUserInfoList[i], fbFullNamesList);
        };
        return fbUniqueNames;
      }
      
      function showStats(playersStatsList) {
    	showNotification("Retrieving players info...");
    	
        var fbUids = dojo.map(playersStatsList, function(playerStats) {
          return playerStats.fbUid;
        });

        FB.Facebook.apiClient.users_getInfo(fbUids, ["first_name", "last_name"], function(result, ex) {
          if (ex) {
        	showNotification("Could not retrieve players info...");
            return;
          }

          showNotification("Rendering...");
          renderStats(playersStatsList, getUniqueNames(result));
          hideNotification();
        });
      }

      function renderRivals(players, playersStats, fbNames, divId,
        countProperty, minCount) {
            
        var html = "<table><tr>" +
          "<td><strong>Player</strong></td>" +
          "<td><strong>Nemesis</strong></td>" +
          "<td><strong>Sidekick</strong></td></tr>";

        for (var i in players) {
          var fbUid = players[i];
          var nemesisFbUid = playersStats[fbUid].nemesisFbUid;
          var sidekickFbUid = playersStats[fbUid].sidekickFbUid;

          var playerStr = fbNames[fbUid];
          if ((countProperty) && (playersStats[fbUid][countProperty] < minCount)) {
            playerStr = "(" + playerStr + ")";
          }
          var nemesisStr = ((nemesisFbUid != 0) ? fbNames[nemesisFbUid] : "None");
          var sidekickStr = ((sidekickFbUid != 0) ? fbNames[sidekickFbUid] : "None");
          
          html += "<tr>" +
            "<td>" + playerStr + "</td>" +
            "<td>" + nemesisStr + "</td>" +
            "<td>" + sidekickStr + "</td>" +
            "</tr>";
        }

        html += "</table>";

        dojo.byId(divId).innerHTML = html;
      }
      
      function renderCountsGraph(players, playersStats, fbNames, divId,
        property, countProperty, minCount) {
            
        var playersByProperty = players.slice(0).sort(function(a, b) {
          return playersStats[b][property] - playersStats[a][property];
        });
        var propertySeries = dojo.map(playersByProperty, function(player) {
          return 0+playersStats[player][property]; // +0 converts to integer?
        });
        var propertyLabels = new Array();
        for (var i = 0; i < playersByProperty.length; ++i) {
          var player = playersByProperty[i];

          var label = fbNames[player];
          if ((countProperty) && (playersStats[player][countProperty] < minCount)) {
            label = "(" + label + ")";
          }
          if (dojo.isIE) {
            var val = playersStats[player][property];
            label += "\n" + round(val,2);
          }
          propertyLabels.push({value: i+1, text: label});
        }
        
        var propertyChart = new dojox.charting.Chart2D(divId);
        propertyChart.addPlot("default", {
          type: "Columns",
          gap: 3,
          font: "normal normal bold 14pt Tahoma",
          fontColor: "white",
          labelOffset: 40
        });
        propertyChart.addSeries("Values", propertySeries, {fill: "lightblue"});
        propertyChart.addAxis("y", {vertical: true, includeZero: true});
        propertyChart.addAxis("x", {majorTickStep:1, minorTicks: false, labels: propertyLabels});

        var anim1 = new dojox.charting.action2d.Highlight(propertyChart, "default");
        var anim2 = new dojox.charting.action2d.Tooltip(propertyChart, "default");

        propertyChart.render();
      }
      
      function renderWinLoseGraph(players, playersStats, fbNames, divId,
        property, countProperty, minCount) {
            
        var playersByProperty = players.slice(0).sort(function(a, b) {
          return playersStats[b][property] - playersStats[a][property];
        });
        var propertyWinnersSeries = dojo.map(playersByProperty, function(player) {
          var val = playersStats[player][property];
          return (val >= 0) ? val+0 : 0; // exp+0 seems to resolve a bug
        });
        var propertyLosersSeries = dojo.map(playersByProperty, function(player) {
          var val = playersStats[player][property];
          return (val < 0) ? val+0 : 0;
        });

	    var propertyLabels = new Array();
	    for (var i = 0; i < playersByProperty.length; ++i) {
          var player = playersByProperty[i];

          var label = fbNames[player];
          if ((countProperty) && (playersStats[player][countProperty] < minCount)) {
            label = "(" + label + ")";
          }
          if (dojo.isIE) {
            var val = playersStats[player][property];
            label += "\n" + round(val,2);
          }
          propertyLabels.push({value: i+1, text: label});
	    }

        var propertyChart = new dojox.charting.Chart2D(divId);
        propertyChart.addPlot("default", {
          type: "Columns",
          gap: 3,
          font: "normal normal bold 14pt Tahoma",
          fontColor: "white",
          labelOffset: 40
        });
        propertyChart.addSeries("Winners", propertyWinnersSeries, {fill: "green"});
        propertyChart.addSeries("Losers", propertyLosersSeries, {fill: "red"});

        propertyChart.addAxis("y", {vertical: true, includeZero: true});
        propertyChart.addAxis("x", {majorTickStep:1, minorTicks: false, labels: propertyLabels});

        var anim1 = new dojox.charting.action2d.Highlight(propertyChart, "default");
        var anim2 = new dojox.charting.action2d.Tooltip(propertyChart, "default");

        propertyChart.render();
      }
    
      function renderStats(playersStatsList, fbNames) {
        // FireFox 3.0.4 fix
        dojo.byId("totals").innerHTML = "";
        dojo.byId("played").innerHTML = "";
        dojo.byId("expectations").innerHTML = "";
        dojo.byId("groupPlayed").innerHTML = "";
        dojo.byId("groupExpectations").innerHTML = "";
        dojo.byId("pokerranks").innerHTML = "";
        dojo.byId("rors").innerHTML = "";
        dojo.byId("rivals").innerHTML = "";
        
        var players = dojo.map(playersStatsList, function(playerStats) {
          return "" + playerStats.fbUid;
        });
        var playersStats = new Object();
        for (var i = 0; i < playersStatsList.length; ++i) {
          var stats = playersStatsList[i];
          stats.total = stats.totalCashOuts - stats.totalBuyIns;
          stats.survival = 1 - stats.riskOfRuin;
          playersStats["" + stats.fbUid] = stats;
        }
      
        var dates = periodToDates(dijit.byId("periodSelect").attr("value"));
        var fromDate = new Date();
        fromDate.setTime(dates.fromDateMs);
        var toDate = new Date();
        toDate.setTime(dates.toDateMs);
        dojo.byId("periodStart").innerHTML = dateToStr(fromDate);
        dojo.byId("periodEnd").innerHTML = dateToStr(toDate);
        
        // HACK: Charts are rendered incorrectly in TabContainers, unless inside
        // the selected tab. A workaround is to switch to each tab before rendering
        // its charts.
        // http://dojotoolkit.org/forum/dojox-dojox/dojox-support/tabcontainer-and-charting
        var selectedTab = dijit.byId("mainTabs").selectedChildWidget;
        
        dijit.byId("mainTabs").selectChild(dijit.byId("globalTab"));
        renderWinLoseGraph(players, playersStats, fbNames, "totals", "total");
        renderCountsGraph(players, playersStats, fbNames, "played", "gamesPlayed");
        renderWinLoseGraph(players, playersStats, fbNames, "expectations", "expectation", "gamesPlayed", 10);
        
        dijit.byId("mainTabs").selectChild(dijit.byId("groupsTab"));
        renderCountsGraph(players, playersStats, fbNames, "groupPlayed", "groupPlayed");
        renderWinLoseGraph(players, playersStats, fbNames, "groupExpectations", "groupExpectation", "groupPlayed", 10);
	      
        dijit.byId("mainTabs").selectChild(dijit.byId("advancedTab"));
        renderCountsGraph(players, playersStats, fbNames, "rors", "survival", "gamesPlayed", 10);
        renderCountsGraph(players, playersStats, fbNames, "pokerranks", "pokerRank", "gamesPlayed", 10);

        dijit.byId("mainTabs").selectChild(dijit.byId("personalTab"));
        renderRivals(players, playersStats, fbNames, "rivals", "gamesPlayed", 10);

        dijit.byId("mainTabs").selectChild(selectedTab);
      }

      google.setOnLoadCallback(function() {
        dojo.require("dojo.parser");
        dojo.require("dijit.layout.BorderContainer");
        dojo.require("dijit.layout.TabContainer");
        dojo.require("dijit.layout.ContentPane");
        dojo.require("dijit.form.FilteringSelect");
        dojo.require("dojox.charting.Chart2D");
        dojo.require("dojox.charting.action2d.Highlight");
        dojo.require("dojox.charting.action2d.Tooltip");
    
        dojo.addOnLoad(onDojoLoad);
      });
    </script>
  </body>
</html>